#include "log.h"
#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#ifdef USE_PTHREAD_MUTEX
#include <pthread.h>
#endif

#define FMT_STR_LEN 32
#define LOG_FILE_PATH_LEN 128

typedef void (*log_print_fp)(int level, const char *file, int line, const char *fmt, va_list args);

typedef struct {
    uint8_t show_color: 1;
    uint8_t show_call: 1;
    uint8_t show_level: 3;
    uint8_t show_reserve: 3;
    char log_file[128];
    uint32_t file_size;
    FILE *fp;
    char fmt[FMT_STR_LEN];
    log_print_fp log_print_func;
    void (*log_print_fmt_func)(int level, const char *file, int line);
    log_timeshift_fp log_timeshift_func;
    callback_fp callback_func;
    log_lock_fp log_lock_func;
    void *cb_data;
    void *lk_data;
} log_conf_t;

static const char *log_get_timestamp(void);

static char *log_str[] = {
    "FATAL", "ERROR", "WARNING", "INFO", "DEBUG", "TRACE"
};
static uint8_t log_color[] = {
    36, 31, 33, 32, 35, 34
};

#ifdef USE_PTHREAD_MUTEX
static pthread_mutex_t log_mutex;
#endif

static void log_print1(int level, const char *file, int line, const char *fmt, va_list args);
static void log_lock1(bool lock, void *data);

static log_conf_t log_conf = {
    .file_size = 1024 * 1024,
    .log_file = "",
    .show_color = true,
    .show_call = true,
    .show_level = LOG_INFO,
    .fp = NULL,
    .log_timeshift_func = log_get_timestamp,
    .log_print_func = log_print1,
    .log_lock_func = log_lock1
};

static void log_lock2(bool lock, void *data)
{
#ifdef USE_PTHREAD_MUTEX
    pthread_mutex_t *mutex = (pthread_mutex_t *)data;
    if (lock) {
        pthread_mutex_lock(mutex);
    } else {
        pthread_mutex_unlock(mutex);
    }
#endif
}

static void log_lock1(bool lock, void *data)
{
#ifdef USE_PTHREAD_MUTEX
        log_conf.lk_data = (void *)&log_mutex;
        data = log_conf.lk_data;
        pthread_mutex_t *mutex = (pthread_mutex_t *)log_conf.lk_data;
        pthread_mutex_init(mutex, NULL);
#endif
        log_conf.log_lock_func = log_lock2;
        log_conf.log_lock_func(lock, data);
}

static const char *log_get_timestamp(void)
{
    time_t rawtime;
    struct tm *info;
    static char buffer[32];
    time(&rawtime);
    info = localtime(&rawtime);
    strftime(buffer, 32, "%Y%m%d-%H:%M:%S", info);
    return buffer;
}

inline void log_set_file_size(uint32_t size){
    log_conf.log_lock_func(true, log_conf.lk_data);
    log_conf.file_size = size;
    log_conf.log_print_func = log_print1;
    log_conf.log_lock_func(false, log_conf.lk_data);
}

inline int log_set_file(char *name)
{
    log_conf.log_lock_func(true, log_conf.lk_data);
    if (strlen(name) > LOG_FILE_PATH_LEN - 1) {
        log_conf.log_lock_func(false, log_conf.lk_data);
        return -1;
    }
    strncpy(log_conf.log_file, name, LOG_FILE_PATH_LEN);

    log_conf.fp = fopen(log_conf.log_file, "a+b");
    log_conf.fp = log_conf.fp ? log_conf.fp : stdout;
    log_conf.show_color = false;

    log_conf.log_print_func = log_print1;
    log_conf.log_lock_func(false, log_conf.lk_data);
    return 0;
}

inline void log_set_color(bool color)
{
    log_conf.log_lock_func(true, log_conf.lk_data);
    log_conf.show_color = color;
    log_conf.log_print_func = log_print1;
    log_conf.log_lock_func(false, log_conf.lk_data);
}

inline void log_set_call(bool call)
{
    log_conf.log_lock_func(true, log_conf.lk_data);
    log_conf.show_call = call;
    log_conf.log_print_func = log_print1;
    log_conf.log_lock_func(false, log_conf.lk_data);
}

void log_set_level(log_level_t level)
{
    log_conf.log_lock_func(true, log_conf.lk_data);
    log_conf.show_level = level;
    log_conf.log_print_func = log_print1;
    log_conf.log_lock_func(false, log_conf.lk_data);
}

inline void log_set_callback(callback_fp cb, void *data)
{
    log_conf.log_lock_func(true, log_conf.lk_data);
    log_conf.callback_func = cb;
    log_conf.cb_data = data;
    log_conf.log_print_func = log_print1;
    log_conf.log_lock_func(true, log_conf.lk_data);
}

inline void log_set_lock(log_lock_fp lock, void *data)
{
#ifndef USE_PTHREAD_MUTEX
    log_lock_fp fp = log_conf.log_lock_func;
    void *lk_data = log_conf.lk_data;
    fp(true, lk_data);
    log_conf.log_lock_func = lock;
    log_conf.cb_data = data;
    log_conf.log_print_func = log_print1;
    fp(true, lk_data);
#endif
}

void log_print2(int level, const char *file, int line, const char *fmt, va_list args)
{
    log_conf.log_lock_func(true, log_conf.lk_data);

    if (log_conf.show_level >= level) {
        if (log_conf.fp != stdout) {
            long size = ftell(log_conf.fp);
            if (size > log_conf.file_size) {
                fclose(log_conf.fp);
                remove(log_conf.log_file);
                log_conf.fp = fopen(log_conf.log_file, "a+b");
                log_conf.fp = log_conf.fp ? log_conf.fp : stdout;
            }
        }

        log_conf.log_print_fmt_func(level, file, line);
        vfprintf(log_conf.fp, fmt, args);
        if (log_conf.show_color) {
            fprintf(log_conf.fp, "\033[0m\n");
        } else {
            fprintf(log_conf.fp, "\n");
        }
        fflush(log_conf.fp);
    }

    log_conf.log_lock_func(false, log_conf.lk_data);
}

void log_print_color_call(int level, const char *file, int line)
{
    fprintf(log_conf.fp, "\033[40;%dm[%-7s]%s %s:%d ", log_color[level], log_str[level],
            log_conf.log_timeshift_func(), file, line);
}

void log_print_color_not_call(int level, const char *file, int line)
{
    fprintf(log_conf.fp, "\033[40;%dm[%-7s]", log_color[level], log_str[level]);
}

void log_print_not_color_call(int level, const char *file, int line)
{
    fprintf(log_conf.fp, "[%-7s]%s %s:%d ", log_str[level], log_conf.log_timeshift_func(), file, line);
}

void log_print_not_color_not_call(int level, const char *file, int line)
{
    fprintf(log_conf.fp, "[%-7s]", log_str[level]);
}


static void log_print1(int level, const char *file, int line, const char *fmt, va_list args)
{
    if (log_conf.fp == NULL) {
        log_conf.fp = stdout;
    }

    log_conf.log_lock_func(true, log_conf.lk_data);
    if (log_conf.fp != stdout) {
        log_conf.fp = fopen(log_conf.log_file, "a+b");
        log_conf.fp = log_conf.fp ? log_conf.fp : stdout;
    }

    log_conf.log_print_func = log_print2;

    if (log_conf.show_call  && log_conf.show_color) {
        log_conf.log_print_fmt_func = log_print_color_call;
    } else if (log_conf.show_color) {
        log_conf.log_print_fmt_func = log_print_color_not_call;
    } else if (log_conf.show_call) {
        log_conf.log_print_fmt_func = log_print_not_color_call;
    } else {
        log_conf.log_print_fmt_func = log_print_not_color_not_call;
    }

    log_conf.log_lock_func(false, log_conf.lk_data);

    log_conf.log_print_func(level, file, line, fmt, args);
}

void log_print(int level, const char *file, int line, const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    log_conf.log_print_func(level, file, line, fmt, args);
    va_end(args);
}
